﻿/*
 Utilities For Wrap.

 Date : 2014/12/3
 Version : 0.5
 
The MIT License (MIT)

Copyright (c) 2014 DavidWTF

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;


namespace Kinect2
{
    using WAITABLE_HANDLE = System.IntPtr;
    using HANDLE = System.IntPtr;
    using DWORD = System.UInt32;

    public interface EventSource
    {
        void Raise(WAITABLE_HANDLE e);
    }

    public interface IOrigin
    {
        object Origin { get; set; }
        Type OriginType { get; set; }
    }

    public class BaseEventArgs<TInterface> : EventArgs, IDisposable, IOrigin
    {
        protected TInterface origin;
        protected Type originType = typeof(TInterface);
        internal BaseEventArgs() : base() { }
        internal BaseEventArgs(TInterface origin) : base()
        {
            if (origin == null)
                throw new NullReferenceException();
            this.origin = origin;
        }
        ~BaseEventArgs()
        {
            Dispose();
        }
        public object Origin
        {
            get { return origin; }
            set { origin = (TInterface)value; }
        }
        public Type OriginType
        {
            get { return originType; }
            set { originType = value; }
        }
        public virtual void Dispose()
        {
            if (origin != null)
                Marshal.ReleaseComObject(origin);
            origin = default(TInterface);
        }
    }

    public class FrameArrivedEventArgs<TInterface, TReference> : BaseEventArgs<TInterface>, IDisposable, IOrigin
        where TReference : IDisposable, IOrigin, new()
    {
        TReference frameReference;
        internal FrameArrivedEventArgs() : base() { }
        internal FrameArrivedEventArgs(TInterface arg) : base(arg) { }

        ~FrameArrivedEventArgs()
        {
            Dispose();
        }
        public TReference FrameReference
        {
            get
            {
                try
                {
                    if (frameReference == null)
                    {
                        frameReference = new TReference();
                        frameReference.Origin = originType.InvokeMember("get_FrameReference", BindingFlags.InvokeMethod, null, origin, null);
                    }
                    return frameReference;
                }
                catch { }
                return default(TReference);
            }
        }
        public override void Dispose()
        {
            if (frameReference != null)
                frameReference.Dispose();
            frameReference = default(TReference);

            base.Dispose();
        }
    }

    public class FrameReference<TInterface, TFrame> : BaseInterfaceWrap<TInterface>
        where TFrame : IOrigin, new()
    {

        internal FrameReference() : base() { }
        internal FrameReference(TInterface arg) : base(arg) { }

        public TimeSpan RelativeTime
        {
            get
            {
                try { return new TimeSpan((long)originType.InvokeMember("get_RelativeTime", BindingFlags.InvokeMethod, null, origin, null)); }
                catch { }
                return new TimeSpan();
            }
        }
        public TFrame AcquireFrame()
        {
            try
            {
                TFrame f = new TFrame();
				f.Origin = originType.InvokeMember("AcquireFrame", BindingFlags.InvokeMethod, null, origin, null);;
				return f;
            }
			catch { }
            return default(TFrame);
        }
    }

    public class FrameSource<TInterface, TFrameReader> : BaseInterfaceWrap<TInterface>
        where TFrameReader : IOrigin, new()
    {
        public FrameSource() : base() { }
        internal FrameSource(TInterface arg) : base(arg) { }

        public bool IsActive
        {
            get { return GetMember<bool>("IsActive"); }
        }
        public KinectSensor KinectSensor
        {
            get { return GetWrappedClass<KinectSensor>("KinectSensor"); }
        }
        public TFrameReader OpenReader()
        {
            try 
            {
                TFrameReader fr = new TFrameReader();
                fr.Origin = originType.InvokeMember("OpenReader", BindingFlags.InvokeMethod, null, origin, null);
                return fr;
            }
            catch { }
            return default(TFrameReader);
        }
    }

    public class FrameSourceWidthEvent<TInterface, TFrameReader, TEventArgs> : BaseEvent<TInterface, TEventArgs>
        where TFrameReader : IOrigin, new()
        where TEventArgs : EventArgs, IOrigin, IDisposable, new()
    {
        public FrameSourceWidthEvent(string subscribeName, string unsubscribeName, string getEventDataName) : base(subscribeName, unsubscribeName, getEventDataName) { }
        internal FrameSourceWidthEvent(TInterface arg, string subscribeName, string unsubscribeName, string getEventDataName) : base(arg, subscribeName, unsubscribeName, getEventDataName) { }

        public bool IsActive
        {
            get { return GetMember<bool>("IsActive"); }
        }
        public KinectSensor KinectSensor
        {
            get { return GetWrappedClass<KinectSensor>("KinectSensor"); }
        }
        public TFrameReader OpenReader()
        {
            try
            {
                TFrameReader fr = new TFrameReader();
                fr.Origin = originType.InvokeMember("OpenReader", BindingFlags.InvokeMethod, null, origin, null);
                return fr;
            }
            catch { }
            return default(TFrameReader);
        }
    }

    public class FrameSourceWithDescription<TInterface, TFrameReader> : FrameSource<TInterface, TFrameReader>
        where TFrameReader : IOrigin, new()
    {
        public FrameSourceWithDescription() : base() { }
        internal FrameSourceWithDescription(TInterface arg) : base(arg) { }

        public FrameDescription FrameDescription
        {
            get
            {
                try
                {
                    FrameDescription fd = new FrameDescription();
                    fd.Origin = originType.InvokeMember("get_FrameDescription", BindingFlags.InvokeMethod, null, origin, null);
                    return fd;
                }
                catch { }
                return null;
            }
        }
    }

    public class BaseEvent<TInterface, TEventArgs> : BaseInterfaceWrap<TInterface>, EventSource
        where TEventArgs : EventArgs, IOrigin, IDisposable, new()
    {
        protected string subscribeName, unsubscribeName, getEventDataName;
        internal BaseEvent(string subscribeName, string unsubscribeName, string getEventDataName) : base() 
        {
            this.subscribeName = subscribeName;
            this.unsubscribeName = unsubscribeName;
            this.getEventDataName = getEventDataName;
        }
        internal BaseEvent(TInterface arg, string subscribeName, string unsubscribeName, string getEventDataName)
            : base(arg)
        {
            this.subscribeName = subscribeName;
            this.unsubscribeName = unsubscribeName;
            this.getEventDataName = getEventDataName;
        }

        protected EventHandler<TEventArgs> eventHandler;
        protected WAITABLE_HANDLE handle = IntPtr.Zero;

        protected virtual void Add_EventHandler(EventHandler<TEventArgs> v)
        {
            if (handle == IntPtr.Zero)
            {
                try
                {
                    handle = (WAITABLE_HANDLE)originType.InvokeMember(subscribeName, BindingFlags.InvokeMethod, null, origin, null);
                    EventRouter.AddWaitableHandle(handle, this);
                }
                catch { }
            }
            eventHandler = (EventHandler<TEventArgs>)(object)System.Delegate.Combine(eventHandler, v);
        }

        protected virtual void Remove_EventHandler(EventHandler<TEventArgs> v)
        {
            eventHandler = (EventHandler<TEventArgs>)System.Delegate.Remove(eventHandler, v);
            if (eventHandler == null && handle != IntPtr.Zero)
            {
                try 
                {
                    EventRouter.RemoveWaitableHandle(handle);
                    object[] p = new object[1];
                    p[0] = handle;
                    originType.InvokeMember(unsubscribeName, BindingFlags.InvokeMethod, null, origin, p);
                }
                catch { }
                handle = IntPtr.Zero;
            }
        }
        protected virtual void RemoveAll_EventHandler()
        {
            eventHandler = null;
            if (handle != IntPtr.Zero)
            {
                try
                {
                    EventRouter.RemoveWaitableHandle(handle);
                    object[] p = new object[1];
                    p[0] = handle;
                    originType.InvokeMember(unsubscribeName, BindingFlags.InvokeMethod, null, origin, p);
                }
                catch { }
                handle = IntPtr.Zero;
            }
        }

        public virtual void Raise(WAITABLE_HANDLE handle)
        {
			try
            {
                using (TEventArgs e = new TEventArgs())
                {
                    object[] p = new object[1];
                    p[0] = handle;
                    e.Origin = originType.InvokeMember(getEventDataName, BindingFlags.InvokeMethod, null, origin, p);

                    if (eventHandler != null)
                        eventHandler(this, e);
                }
            }
			catch {}
        }

        public override void Dispose()
        {
            base.Dispose();
            RemoveAll_EventHandler();
        }
    }

    public class FrameReader<TInterface, TEventArgs> : BaseEvent<TInterface, TEventArgs>
        where TEventArgs : EventArgs, IOrigin, IDisposable, new()
    {
        internal FrameReader() : base("SubscribeFrameArrived", "UnsubscribeFrameArrived", "GetFrameArrivedEventData") { }
        internal FrameReader(TInterface arg) : base(arg, "SubscribeFrameArrived", "UnsubscribeFrameArrived", "GetFrameArrivedEventData") { }
        public bool IsPaused
        {
            get
            {
                try { return (bool)originType.InvokeMember("gut_IsPaused", BindingFlags.InvokeMethod, null, origin, null); }
                catch { }
                return false;
            }
            set
            {
                try
                {
                    object[] p = new object[1];
                    p[0] = value;
                    originType.InvokeMember("put_IsPaused", BindingFlags.InvokeMethod, null, origin, p);
                }
                catch { }
            }
        }
        public event EventHandler<TEventArgs> FrameArrived
        {
            add { Add_EventHandler(value); }
            remove { Remove_EventHandler(value); }
        }
    }

    public class FrameReaderWithAcquire<TInterface, TFrame, TEventArgs> : FrameReader<TInterface, TEventArgs>
        where TEventArgs : EventArgs, IOrigin, IDisposable, new()
        where TFrame : IOrigin, new()
    {
        internal FrameReaderWithAcquire() : base() { }
        internal FrameReaderWithAcquire(TInterface arg) : base(arg) { }
        public TFrame AcquireLatestFrame()
        {
            try
            {
                TFrame f = new TFrame();
                f.Origin = originType.InvokeMember("AcquireLatestFrame", BindingFlags.InvokeMethod, null, origin, null);
                return f;
            }
            catch { }
            return default(TFrame);
        }
    }

    public class BaseInterfaceWrap<TInterface> : IDisposable, IOrigin, IEquatable<BaseInterfaceWrap<TInterface>>
    {
        protected TInterface origin;
        protected Type originType = typeof(TInterface);
        internal BaseInterfaceWrap()
        {
        }
        internal BaseInterfaceWrap(TInterface origin)
        {
            if (origin == null)
                throw new NullReferenceException();
            this.origin = origin;
        }
        ~BaseInterfaceWrap()
        {
            Dispose();
        }
        public virtual Object Origin
        {
            get { return origin; }
            set { origin = (TInterface)value; }
        }
        public Type OriginType
        {
            get { return originType; }
            set { originType = value; }
        }
        protected T GetMember<T>(string name)
        {
            try
            {
                return (T)originType.InvokeMember("get_" + name, BindingFlags.InvokeMethod, null, origin, null);
            }
			catch { }
            return default(T);
        }
        protected void PutMember<T>(string name, T value)
        {
            try
            {
                object[] p = new object[1];
                p[0] = value;
                originType.InvokeMember("put_" + name, BindingFlags.InvokeMethod, null, origin, p);
            }
            catch { }
        }
        protected T GetWrappedClass<T>(string name)
            where T : IOrigin, new()
        {
            try
            {
                T s = new T();
                s.Origin = originType.InvokeMember("get_" + name, BindingFlags.InvokeMethod, null, origin, null);
                return s;
            }
            catch { }
            return default(T);
        }
        protected TimeSpan GetTimeSpan(string name)
        {

            try { return new TimeSpan((long)originType.InvokeMember("get_" + name, BindingFlags.InvokeMethod, null, origin, null)); }
            catch { }
            return new TimeSpan();
        }
        public static bool operator !=(BaseInterfaceWrap<TInterface> a, BaseInterfaceWrap<TInterface> b)
        {
            return !(a == b);
        }
        public static bool operator ==(BaseInterfaceWrap<TInterface> a, BaseInterfaceWrap<TInterface> b)
        {
            if (object.ReferenceEquals(a, b))
                return true;

            if (((object)a == null) || ((object)b == null))
               return false;
             return (object)a.origin == (object)b.origin;
        }
        public override int GetHashCode()
        {
			return origin.GetHashCode();
        }
		public override bool Equals(object other)
        {
			if (other == null)
				return false;

            BaseInterfaceWrap<TInterface> t = other as BaseInterfaceWrap<TInterface>;
            if ((object)t == null)
                return false;

            return (object)t.origin == (object)origin;
        }
        public bool Equals(BaseInterfaceWrap<TInterface> other)
        {
            if ((object)other == null)
                return false;
            return (object)other.origin == (object)origin;
        }
        public virtual void Dispose()
        {
            if (origin != null)
                Marshal.ReleaseComObject(origin);
            origin = default(TInterface);
        }
    }

    public class EventRouter
    {
        static EventRouter eventRouter;
        Dictionary<WAITABLE_HANDLE, EventSource> waitableHandles = new Dictionary<WAITABLE_HANDLE, EventSource>();
        [DllImport(@"kernel32.dll", SetLastError = true)]
        static extern DWORD WaitForSingleObject(
            [In] HANDLE hHandle,
            [In] DWORD dwMilliseconds);
        [DllImport(@"kernel32.dll", SetLastError = true)]
        static extern DWORD WaitForMultipleObjects(
            [In] DWORD nCount,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] HANDLE[] lpHandles,
            [In] bool bWaitAll,
            [In] DWORD dwMilliseconds);

		[DllImport(@"kernel32.dll", SetLastError = true)]
		static extern DWORD WaitForMultipleObjectsEx(
			[In] DWORD nCount,
			[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] HANDLE[] lpHandles,
			[In] bool bWaitAll,
			[In] DWORD dwMilliseconds,
			[In] bool bAlertable);

		[DllImport(@"kernel32.dll", SetLastError = true)]
        static extern DWORD MsgWaitForMultipleObjects(
            [In] DWORD nCount,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] HANDLE[] pHandles,
            [In] bool fWaitAll,
            [In] DWORD dwMilliseconds,
            [In] DWORD dwWakeMask);
        [DllImport(@"kernel32.dll", SetLastError = true)]
        static extern bool ResetEvent(
            [In] HANDLE hEvent);


        static DWORD WAIT_OBJECT_0 = 0;
        public static EventRouter Get()
        {
            if (eventRouter == null)
                eventRouter = new EventRouter();
            return eventRouter;
        }
        public static void AddWaitableHandle(WAITABLE_HANDLE e, EventSource source)
        {
            Get().waitableHandles[e] = source;
        }
        public static void RemoveWaitableHandle(WAITABLE_HANDLE e)
        {
            Get().waitableHandles.Remove(e);
        }
		static void Raise(HANDLE h)
		{
			EventRouter er = Get ();
			EventSource es;
			if (er.waitableHandles.TryGetValue(h, out es))
				es.Raise(h);
		}
        public static void Check()
        {
			EventRouter er = Get ();
			List<HANDLE> list = new List<WAITABLE_HANDLE> (er.waitableHandles.Keys.Count);
			foreach (HANDLE h  in er.waitableHandles.Keys)
				list.Add (h);
			if (list.Count > 0)
			{
				HANDLE[] handles = new HANDLE[1];
				foreach (WAITABLE_HANDLE h in list)
				{
					handles[0] = h;
					DWORD r = WaitForSingleObject(h, 0);
					if (WAIT_OBJECT_0 == r)
					{
						Raise(h);
					}
				}
			}
        }
    }
}
